//
//  SocketEnginePollable.m
//  socket.io-OC
//
//  Created by leeco on 16/8/9.
//  Copyright © 2016年 leeco. All rights reserved.
//

#import "SocketEnginePollable.h"
#import "SocketStringReader.h"


@interface SocketEnginePollable ()

@end

@implementation SocketEnginePollable

@synthesize invalidated;
@synthesize session;
@synthesize waitingForPost;
@synthesize waitingForPoll;
@synthesize postWait;

//协议扩展实现方法
- (void)addHeaders:(NSMutableURLRequest *)req
{
    if (self.cookies) {
        NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:self.cookies];
        req.allHTTPHeaderFields = headers;
    }
    
    if (self.extraHeaders) {
        [self.extraHeaders enumerateKeysAndObjectsUsingBlock:^(id headerName, id value, BOOL * _Nonnull stop) {
            [req setValue:value forHTTPHeaderField:headerName];
        }];
    }
}

- (NSURLRequest *)createRequestForPostWithPostWait
{
    NSMutableString *postStr = [NSMutableString string];
    
    for (NSString *packet in self.postWait) {
        NSInteger len = [packet length];
        
        [postStr appendFormat:@"%ld:%@",(long)len,packet];
    }
    
    // TODO: DefaultSocketLogger
    [self.postWait removeAllObjects];
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:self.urlPollingWithSid];
    
    [self addHeaders:req];
    
    req.HTTPMethod = @"POST";
    [req setValue:@"text/plain; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
    
    NSData *postData = [postStr dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
    
    req.HTTPBody = postData;
    [req setValue:[NSString stringWithFormat:@"%ld",postData.length] forHTTPHeaderField:@"Content-Length"];
    
    return req;
}

- (void)doPoll
{
    if (self.websocket || waitingForPoll || !self.connected || self.closed) {
        return;
    }
    
    waitingForPoll = YES;
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:self.urlPollingWithSid];
    
    [self addHeaders:req];
    [self doLongPoll:req];
}

- (void)doRequest:(NSURLRequest *)req withCallback:(void (^)(NSData *, NSURLResponse *, NSError *))callback
{
    if (self.polling || self.closed || invalidated || self.fastUpgrade) {
        // TODO: DefaultSocketLogger
        return;
    }
    
    // TODO: DefaultSocketLogger
    
    [[session dataTaskWithRequest:req completionHandler:callback] resume];
}

- (void)doLongPoll:(NSURLRequest *)req
{
    __weak typeof(self) this = self;
    [self doRequest:req withCallback:^(NSData *data, NSURLResponse *res, NSError *err) {
        if (!this.polling) {
            return;
        }
        
        if (err != nil || data == nil) {
            // TODO: DefaultSocketLogger
            
            if (this.polling) {
                [this didError:[err localizedDescription] ? [err localizedDescription] : @"Error"];
            }
            
            return;
        }
        
        // TODO: DefaultSocketLogger
        
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        dispatch_async(this.parseQueue, ^{
            [this parsePollingMessage:str];
        });
        
        this.waitingForPoll = NO;
        
        if (this.fastUpgrade) {
            [this doFastUpgrade];
        }else if (!this.closed && this.polling){
            [this doPoll];
        }
    }];
}

- (void)flushWaitingForPost
{
    if (postWait.count == 0 || !self.connected) {
        return;
    }else if (self.websocket){
        [self flushWaitingForPostToWebSocket];
        return;
    }
    
    NSURLRequest *req = [self createRequestForPostWithPostWait];
    
    waitingForPost = YES;
    
    // TODO: DefaultSocketLogger
    
    __weak typeof(self) this = self;
    [self doRequest:req withCallback:^(NSData *data, NSURLResponse *res, NSError *err) {
        if (err != nil) {
            // TODO: DefaultSocketLogger
            
            if (this.polling) {
                [this didError:[err localizedDescription] ? [err localizedDescription] : @"Error"];
            }
            
            return ;
        }
        
        this.waitingForPost = NO;
        
        dispatch_async(this.emitQueue, ^{
            if (!this.fastUpgrade) {
                [this flushWaitingForPost];
                [this doPoll];
            }
        });
    }];
    
}

- (void) parsePollingMessage:(NSString *)str
{
    if (str.length == 1) {
        return;
    }
    
    SocketStringReader *reader = [[SocketStringReader alloc] initWithMessage:str];
    
    while ([reader hasNext]) {
        NSString *len_str = [reader readUntilStringOccurence:@":"];
        if (len_str) {
            NSInteger n = [str integerValue];
            NSString *str = [reader read:n];
            
            __weak typeof(self) this = self;
            dispatch_async(self.handleQueue, ^{
                [this parseEngineMessage:str fromPolling:YES];
            });
        }else{
            __weak typeof(self) this = self;
            dispatch_async(self.handleQueue, ^{
                [this parseEngineMessage:str fromPolling:YES];
            });
            break;
        }
    }
}

/// Send polling message.
/// Only call on emitQueue
- (void)sendPollMessage:(NSString *)message withType:(SocketEnginePacketType)type withData:(NSArray<NSData *> *)datas
{
    // TODO: DefaultSocketLogger
    NSString *fixedMessage;
    
    if (self.doubleEncodeUTF8) {
        fixedMessage = [self doubleEncodeUTF8:message];
    }else{
        fixedMessage = message;
    }
    
    NSString *strMsg = [NSString stringWithFormat:@"%d%@",type,fixedMessage];
    
    [postWait addObject:strMsg];
    
    for (NSData *data in datas) {
        // TODO: 此处可能存在问题
        Either *either = [self createBinaryDataForSend:data];
        if (either.Right && [either.Right isKindOfClass:[NSString class]]) {
            [postWait addObject:either.Right];
        }
    }
    
    if (!self.waitingForPost) {
        [self flushWaitingForPost];
    }
}

- (void)stopPolling
{
    self.waitingForPoll = NO;
    self.waitingForPost = NO;
    [session finishTasksAndInvalidate];
}

@end
